# coding=utf-8

import base64
import string
import random, re
import math
import json
import time, datetime
import urllib, urlparse, urllib2
import email

import web
from web.utils import re_compile
from pyDes import *

import settings

__all__ = (
	'randstr', 'is_email', 'md5', 'cmp_i',
	'encrypt', 'decrypt', 'format_absurl',
    'intval',
)

def safe_format(src):
    """提供安全的格式化字符串"""
    if src:
        return src.replace('%', '%%')
    return src

web.template.Template.globals['safe_format'] = safe_format

def base64_encode(src):
    """base64 编码"""
    return base64.b64encode(src)

def base64_decode(src):
    """base64 解码"""
    return base64.b64decode(src)

def post_office(email):
    """获取给定email的邮局地址"""
    if not is_email(email):
        return 'javascript:;'

    # 获取邮箱后缀
    po = email.split('@')[1]
    # 只有少数几个的邮局网站需要特殊的处理
    # gmail算一个
    if cmp_i('gmail.com', po) == 0:
        return 'www.gmail.com'
    else:
        return 'mail.%s' % po

def encrypt_cookie(name, value):
    """
    设置并加密 COOKIE
    """
    web.setcookie(name, encrypt(value))

def decrypt_cookie(name):
    """
    获取并解密 COOKIE
    """
    return decrypt(web.cookies().get(name, None))

def deg2rad(d):
    """degree to radian"""
    return d * math.pi/180.0

EARTH_RADIUS_METER = 6378137.0
def distance(f, t):
    """计算球形上两个点的距离"""
    flon = deg2rad(f[1])
    flat = deg2rad(f[0])
    tlon = deg2rad(t[1])
    tlat = deg2rad(t[0])
    con = math.sin(flat) * math.sin(tlat)
    con += math.cos(flat) * math.cos(tlat) * math.cos(flon - tlon)
    return math.acos(con) * EARTH_RADIUS_METER

def short_url(url):
    """
    将给定url缩短
    """
    result = POST('http://126.am/api!shorten.action'
            , {
                'key': settings.AM_126_APP_KEY,
                'longUrl': url})
    if result:
        json = json_decode(result)
        if json and json['status_code'] == 200:
            return json['url']
    
    return url

def POST(url, data):
    """
    向给定url发送POST请求，并返回结果
    """
    request = urllib2.Request(
            url = url,
            headers = {
                'Accept-Charset': 'GBK,utf-8;'
            },
            data = urllib.urlencode(data))

    try:
        httpresp = urllib2.urlopen(request)
        return httpresp.read()
    except:
        raise
    finally:
        httpresp.close()

def date_is_expire(date):
    """
    判断给定日期是否超时
    """
    if date_is_valid(date):
        return date <= datetime.date.today()

    return False

web.template.Template.globals['date_is_expire'] = date_is_expire

def date_is_valid(date):
    """
    判断一个日期是否有效
    """
    return date and str(date) != '1982-06-18'

def sign_discount(item):
    """
    为给定的特惠信息生成数字签名
    """
    return md5('/discounts/%s$|$TiAnZhEn' % item.id)

web.template.Template.globals['sign_discount'] = sign_discount

def request_from_spread():
    """
    判断请求是否来自推广渠道
    """
    referer = get_referer()
    if referer:
        try:
            url = urlparse.urlparse(referer)
            # 获取链接的域名
            domain = url.netloc
            # 域名是否包含端口
            if ':' in domain: 
                domian = domain.split(':')[0]
            # 域名是否在推广域名中
            if domain in ('t.cn', 'url.cn', 'weibo.com', 't.qq.com',):
                return True
            # 请求是否包含给定的域名片段
            # 如果包含这些域名片段，直接允许访问，不要求订阅
            for dt in ('google.', 'baidu.', 'bing.', 'sogou.', 'soso.', 'qq.',):
                if dt in domain:
                    return True
        except:
            return False
    return False

def get_referer():
    """
    获取 HTTP_REFERER
    """
    return web.ctx.env.get('HTTP_REFERER', None)

def get_client_ip():
    "获取客户端IP"
    if web.ctx:
        return web.ctx.ip
    return '127.0.0.1'

def request(key, value = None):
    """
    使用 web.input() 获取客户端提交内容
    @key as str, 要获取的内容项
    @value as str, 当key代表的值不存在时，默认返回的值
    """
    return web.input().get(key, value)

web.template.Template.globals['request'] = request

def generate_subscribe_auth_url(sign):
    """
    生成给定用户的订阅确认地址
    @sign as str, 数字签名
    """
    return 'http://xunbaotu.com/subscribe?auth=%s' % sign

def week_mail(recvs, subject, content):
    """
    发送每周特惠订阅邮件（使用week@xunbaotu.com）
    @recvs as [storage(name, email)], 接受邮件的人
    @subject as str, 邮件标题
    @content as str, 邮件内容
    """
    return sendmail('=?UTF-8?B?%s?= <week@xunbaotu.com>' % base64_encode('寻宝图旅游编辑团 ')
            , recvs
            , subject
            , content)

def service_mail(recvs, subject, content):
    """
    发送服务邮件（使用service@xunbaotu.com）
    @recvs as [storage(name, email)], 接受邮件的人
    @subject as str, 邮件标题
    @content as str, 邮件内容
    """
    return sendmail('=?UTF-8?B?%s?= <service@xunbaotu.com>' % base64_encode('寻宝图')
            , recvs
            , subject
            , content)

def sendmail(sender, recvs, subject, content):
    """
    发送邮件
    @sender as str, 发送人的邮件地址
    @recvs as [storage(name, email)], 接受邮件的人
    @subject as str, 邮件标题
    @content as str, 邮件内容
    """
    if not recvs:
        # 如果接受人无效
        return False
    # 设置邮件发送的账户
    web.config.smtp_username = email.Utils.parseaddr(sender)[1]
    # 编码标题，避免某些邮箱乱码
    subject = '=?UTF-8?B?%s?=' % base64.b64encode(subject)
    try:
        # 发送邮件
        web.sendmail(sender, recvs, subject, content
                , headers = ({
                        'User-Agent': 'Xunbaotu.com Mail Server',
                        'X-Mailer': 'Xunbaotu.com Mail MIA',
                        'Content-Type': 'text/html',
                        }
                    ))
        return True
    except:
        # 报错，发送失败
        raise
        return False

def urlencode(url):
    """
    url编码
    """
    return urllib.quote(url)

def json_encode(obj):
    """
    json编码
    """
    return json.dumps(obj)

def json_decode(src):
    """
    json代码解码
    """
    try:
        return json.loads(src)
    except:
        return None

def int2monthday(src):
    """
    将 yyyymmdd 格式的时间转换为 mm月dd日 格式
    """
    src = str(src)
    if len(src) != 8:
        return src
    
    return u'%s月%s日' % (src[4:6], src[6:8])


def int2date(src, fmt = '%(year)s-%(month)s-%(day)s'):
    """
    将 yyyymmdd 格式的整数日期，
    转换为 yyyy-mm-dd 格式的字符串
    """
    src = str(src)
    if len(src) != 8:
        return src

    return fmt % {'year':src[:4], 'month':src[4:6], 'day':src[6:8]}

# 将时间转换方法存入模板引擎中
web.template.Template.globals['int2date'] = int2date

def moneyfmt(number, isfmt = True, initial = False, curr = u'￥'):
    """
    将给定数字转换为货币格式
    @number as long, 需要转换的数字
    @curr as str, 货币符号
    @initial as bool, 如果为True，会在价格后增加一个“起”字
    """
    fmt = str(number)
    if isfmt:
        fmt = re.sub(r"(\d\d\d)(?=\d)(?!\d*\.)"
                , r"\1,"
                , fmt[::-1])[::-1]
    return '%s%s%s' % (curr, fmt, initial and u'起' or '',)

# 将 moneyfmt 方法存入模板全局对象中
web.template.Template.globals['moneyfmt'] = moneyfmt

def create_media_part(id):
    """
    根据给定id，创建媒体文件路径的关键部分
    """
    # 完整的媒体文件长度
    ret = '000000000'
    # 获取id长度
    length = len(str(id))
    # 长度若不够，补位
    if length < len(ret):
        # 给id前面补0，直至9位
        ret = '%s%s' % (ret[:-length], id)
        # 从左至右，每3位一级目录
    
    # 000/000/001
    return '%s/%s/%s' % (ret[:3], ret[3:6], ret[6:len(ret)])

def get_media_path(id, thumbnail = 1, extension = '.jpg'):
    """
    获取媒体文件路径，本方法将会将远程文件本地化
    对性能有影响，所以请慎用
    目前仅让微博发图使用即可
    """
    # 获取媒体文件的路径
    path = get_media_url(id, thumbnail, extension)
    # 路径中是否包含http://
    # 如果包含即为远程文件，需要先下载
    if path.startswith('http://'):
        # 下载远程文件，返回临时地址
        try:
            (path, h) = urllib.urlretrieve(path)
        except:
            return False
    else:
        # 本地文件
        # 本地文件是从网站的根目录下的/static目录内的
        path = os.path.join(settings.APP_DIR, path[1:])
        # 这个实现方法不是很标准的
        # 但是满足现在的需求
        # 必要时，再进一步扩展
    return path


# 媒体未找到时显示的图片
media_404 = '/static/images/common/media_404.jpg'
def get_media_url(id, thumbnail = 1, extension = '.jpg'):
    """
    获取给定媒体文件路径
    @id as (int ,long), media的内码
    @thumbnail as int, 缩略图级别，0 - 源图，数字越大图片尺寸越小
    @mimetype as string, 图片格式，只有当 thumbnail = 0
                         即获取源图时，该参数才有效
                         默认情况下，缩略图，采用的是 -
                         settings.MEDIA_THUMBNAIL_EXTENSION设置的格式保存
    """
    if not id:
        return settings.MEDIA_404
    # 传入的id必须是整数
    if not isinstance(id, (int, long)):
        if isinstance(id, (str, unicode)):
            # 如果id是一个字符串
            # 那么合法的格式，就是以半角逗号间隔的数字
            # 取随机id作为特色图
            idlst = id.split(',')
            # 随机取一张
            id = intval(random.choice(idlst))
        else:
            return settings.MEDIA_404
    
    if id <= 0:
        return settings.MEDIA_404

    # 获取关键部分路径
    part = create_media_part(id)
    # 是否后去缩略图
    if thumbnail > 0:
        return '%s/%s-%s%s' % (settings.MEDIA_ROOT
                            , part
                            , thumbnail
                            , settings.MEDIA_THUMBNAIL_EXTENSION)
    else:
        return '%s/%s%s' % (settings.MEDIA_ROOT
                            , part
                            , extension)

# 将 get_meida_url 方法存入模板全局对象中
web.template.Template.globals['get_media_url'] = get_media_url

def intval(src, val = 0):
    """
    将给定字符串转换为int
    如果转换失败，返回val，val默认为0
    """
    try:
        v = int(src)
        return v
    except:
        return val

# TODO: 将给定相对路径转换为绝对路经
def format_absurl(url):
    '将给定相对路径转换为绝对路径'
    return 'http://%s%s' % (settings.CONFIG.DOMAIN, url)

# TODO: 判断给定文件是否是有效的图片文件
def is_image(f):
    if not f:
        return False

    # 需要PIL包支持
    import Image
    try:
        im = Image.open(f)
        # 如果是有效得图片文件
        # 判断是否是允许得图片类型
        #return im.format in exts
        return im
    except:
        return False

# TODO: 判断给定字符串是否是数字
def is_unsign_integer(src):
    if src:
        for char in str:
            if char not in string.digits:
                return False
        return True
    else:
        return False

# 随机字符串选取范围
_seeks = string.letters + '0123456789'
# TODO: 获取指定长度的随机字符串
def randstr(len = 10):
	return ''.join(random.choice(_seeks) for i in xrange(len))

html_tags_pattern = ['<script[^>]*?>.*?</script>'
                    , '<[\/\!]*?[^<>]*?>'
                    #, '([\r\n])[\s]+'
                    , '&(quot|#34);'
                    , '&(amp|#38);'
                    , '&(lt|#60);'
                    , '&(gt|#62);'
                    , '&(nbsp|#160);']
def html_clear(src):
    '清除给定字符串中的HTML代码'
    if src:
        for pattern in html_tags_pattern:
            p = re_compile(pattern, re.IGNORECASE)
            src = p.sub('', src)
    return src

def sqlescape(s):
    """
    过滤掉可能引起SQL注入漏洞的字符串
    """
    if s:
        p = re_compile("['=%]", re.IGNORECASE)
        s = p.sub('', s)

    return s


# TODO: 判断制定字符串是否是正确的Email地址格式
def is_email(src):
    if src:
        p = re_compile('^([a-z0-9_\.\-\+]+)@([\da-z\.\-]+)\.([a-z\.]{2,6})$', re.IGNORECASE)
        return bool(p.match(src))
    return False

# TODO: md5加密给定字符串
def md5(src):
	if src:
		import hashlib
		m = hashlib.md5(src)
		return m.hexdigest()

	return None

# TODO: 忽略大小写比较两个字符串是否相等
def cmp_i(lhs, rhs):
	return cmp(lhs.lower(), rhs.lower())

# TODO: 获取des对象
def _des():
	return des('DESCRYPT', CBC, '\5\2\3\6\5\6\7\2', pad=None, padmode=PAD_PKCS5)

# TODO: 加密给定字符串
def encrypt(src):
	return src and _des().encrypt(src.encode('utf-8')) or None

# TODO: 解密给定字符串
def decrypt(src):
	try:
		return src and _des().decrypt(src).decode('utf-8') or None
	except:
		return None

class Pager(object):
    """
    分页类
    """
    def __init__(self, totalrecords, **attrs):
        # 在记录数
        self.totalrecords = totalrecords
        # 当前页码
        self.pageindex = attrs.pop('pageindex', 0)
        # 每页记录数
        self.pagesize = attrs.pop('pagesize', 15)
        # 当前选中页两边的页码数
        self.pagelen = attrs.pop('pagelen', 3)
        # “首页”的显示文本
        self.firsttext = attrs.pop('firsttext', '<<')
        # “上一页”的显示文本
        self.prevtext = attrs.pop('prevtext', '<')
        # “下一页”的显示文本
        self.nexttext = attrs.pop('nexttext', '>')
        # “尾页”的显示文本
        self.lasttext = attrs.pop('lasttext', '>>')
        # 链接模板
        # %s 表示页码
        self.link = attrs.pop('link', '<a href="?page=%(page)s">%(text)s</a>')
        # 当前页模板
        self.current = attrs.pop('current', '<strong>%(text)s</strong>')
        # 下一页模板
        self.next = attrs.pop('next', self.link)
        # 上一页模板
        self.prev = attrs.pop('prev', self.link)
        # 首页模板
        self.first = attrs.pop('first', self.link)
        # 尾页模板
        self.last = attrs.pop('last', self.link)
        # 附加参数
        self.params = attrs.pop('params', {})

    def render(self):
        # 附件参数
        paras = urllib.urlencode(self.params)
            
        # 计算总页数
        self.totalpages = int(math.ceil(float(self.totalrecords) / self.pagesize))
        # 确保当前页处在安全范围内容
        self.pageindex = min(self.pageindex, self.totalpages)
        self.pageindex = max(self.pageindex, 0)

        html = ''
        if self.pageindex > 0:
            if self.first:
                html += self.first % {
                        'page': 0, 
                        'text': self.firsttext, 
                        'params': paras,
                    }
            if self.prev:
                html += self.prev % {
                        'page': self.pageindex - 1, 
                        'text': self.prevtext,
                        'params': paras,
                    }

        # 页码列表
        begin = max(self.pageindex - self.pagelen, 0)
        end = min(self.pageindex + self.pagelen + 1, self.totalpages)
        while begin < end:
            pindex = begin + 1
            if begin != self.pageindex:
                html += self.link % {
                        'page': begin, 
                        'text':pindex,
                        'params': paras,
                    }
            else:
                html += self.current % {'text':pindex}

            begin += 1

        if self.pageindex < self.totalpages - 1:
            if self.next:
                html += self.next % {
                        'page': self.pageindex + 1, 
                        'text': self.nexttext,
                        'params': paras,
                    }
            if self.last:
                html += self.last % {
                        'page': self.totalpages - 1, 
                        'text': self.lasttext,
                        'params': paras,
                    }

        return html

    def __str__(self):
        return self.render()

def lite_pager(totalrecords, pageindex, **kw):
    """简洁模式下的分页实例"""
    # 附加参数
    return Pager(totalrecords, pageindex = pageindex
                        , first = False, last = False
                        , link = '<a class="page-numbers" href="?%(params)s&page=%(page)s">%(text)s</a>&nbsp;'
                        , current = '<span class="page-numbers current">%(text)s</span>&nbsp;'
                        , next = '<a class="next page-numbers" href="?%(params)s&page=%(page)s">%(text)s</a>'
                        , prev = '<a class="prev page-numbers" href="?%(params)s&page=%(page)s">%(text)s</a>&nbsp;'
                        , nexttext = '»'
                        , prevtext = '«'
                        , params = kw)
